// package main 代表的是一个可运行的应用程序，
// 而 main 函数则是这个应用程序的主入口。
// 在 Go 语言中 main 包是一个特殊的包，代表你的 Go 语言项目是一个可运行的应用程序，而不是一个被其他项目引用的库。
package main

import (
	"errors"
	"fmt"
	"strconv"
	"strings"
	"unicode/utf8"
)

func main() {
	fmt.Println("Hello, 世界")

	// 基础
	// base()

	fmt.Println("-----------------------------")

	// 分支判断
	// testIf()
	// testSwitch()
	// testFor()
	// testArray()
	// testSlice()
	// testMap()

	result := sum(3, 4)
	fmt.Println(result)

	//XXX 多值返回
	result, err := sum2(1, 2)
	// 忽略错误信息
	// result,_ := sum(1, 2)
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println(result, "aaaa")
	}

	//XXX 闭包
	cl := colsure()
	fmt.Println(cl())

	//XXX 方法
	age := Age(25)
	age.String()
	//方法赋值给变量，方法表达式
	sm := Age.String
	//通过变量，要传一个接收者进行调用也就是age
	sm(age)

	//XXX 结构体
	// var p person
	// 不指定属性初始化
	// p := person{"测试", 30}
	// 指定属性初始化
	// p := person{age: 30, name: "飞雪无情"}
	// fmt.Println(p)
	// fmt.Println(p.name, p.age)
	p := person{
		age:  30,
		name: "测试",
		addr: address{
			province: "四川",
			city:     "成都",
		},
	}
	fmt.Println(p)
	fmt.Println(p.addr.province)

	//XXX 接口
	printString(p)
	// 指针： 以值类型接收者实现接口的时候，不管是类型本身，还是该类型的指针类型，都实现了该接口。
	printString(&p)
	printString(p.addr)

	// 异常
	testError()

	// defer
	testMoreDefer()
}

func testMoreDefer() {
	defer fmt.Println("First defer")
	defer fmt.Println("Second defer")
	defer fmt.Println("Three defer")
	fmt.Println("函数自身代码")
}

func testError() {
	// ! 这是一个测试注释
	i, err := strconv.Atoi("a")
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println(i)
	}
}

// printString 这个函数的优势就在于它是面向接口编程的，只要一个类型实现了 Stringer 接口，都可以打印出对应的字符串，而不用管具体的类型实现。
func printString(s fmt.Stringer) {
	fmt.Println(s.String())
}

func (addr address) String() string {
	return fmt.Sprintf("the addr is %s%s", addr.province, addr.city)
}

func (p person) String() string {
	return fmt.Sprintf("the name is %s,age is %d", p.name, p.age)
}

// 接收者改成指针类型
// func (p *person) String()  string{
//     return fmt.Sprintf("the name is %s,age is %d",p.name,p.age)
// }

// type Stringer interface {
// 	String() string
// }

type person struct {
	name string
	age  uint
	addr address
}

type address struct {
	province string
	city     string
}

// 方法
type Age uint

// 值返回值
func (age Age) String() {
	fmt.Println("the age is", age)
}

// 指针返回值
func (age *Age) Modify() {
	*age = Age(30)
}

// 闭包
func colsure() func() int {
	i := 0
	return func() int {
		i++
		return i
	}
}

// 可变参数，可变参数的类型其实就是切片
func sum4(params ...int) int {
	sum := 0
	for _, i := range params {
		sum += i
	}
	return sum
}

/*
*
返回值的命名和参数、变量都是一样的，名称在前，类型在后。以上示例中，命名的两个返回值名称，一个是 sum，一个是 err，这样就可以在函数体中使用它们了。
*/
func sum3(a, b int) (sum int, err error) {
	if a < 0 || b < 0 {
		return 0, errors.New("a或者b不能是负数")
	}

	sum = a + b
	err = nil
	return
}

// 多值返回
func sum2(a, b int) (int, error) {
	if a < 0 || b < 0 {
		return 0, errors.New("a或者b不能是负数")
	}
	return a + b, nil
}

// func sum(a , b int) int {
func sum(a int, b int) int {
	return a + b
}

func base() {
	var i int = 10
	fmt.Println(i)

	// 正数
	var (
		j int = 123
		k int = 456
	)
	fmt.Println(j)
	fmt.Println(k)

	// 浮点数
	var f32 float32 = 2.2
	var f64 float64 = 10.3456
	fmt.Println("f32 is", f32, ",f64 is", f64)

	// bool
	var bf bool = false
	var bt bool = true
	fmt.Println("bf is", bf, ",bt is", bt)

	// String
	var s1 string = "Hello"
	var s2 string = "世界"
	fmt.Println("s1 is", s1, ",s2 is", s2)
	fmt.Println("s1 + s2 is", s1+s2)

	// 默认值
	var zi int
	var zf float64
	var zb bool
	var zs string
	fmt.Println(zi, zf, zb, zs)

	// 变量简短声明
	var x = 12
	fmt.Print(x)
	i2 := 10
	bfs := false
	s3 := "Hello"
	fmt.Println(i2, bfs, s3)

	//判断s1的前缀是否是H
	fmt.Println(strings.HasPrefix(s1, "H"))
	//在s1中查找字符串o
	fmt.Println(strings.Index(s1, "o"))
	//把s1全部转为大写
	fmt.Println(strings.ToUpper(s1))
	fmt.Println(strings.Contains(s1, "llo"))
}

func testIf() {
	// i := 6
	if i := 6; i > 10 {
		fmt.Println("i>10")
	} else if i > 5 && i <= 10 {
		fmt.Println("5<i<=10")
	} else {
		fmt.Println("i<=5")
	}
}

func testSwitch() {
	switch i := 6; {
	case i > 10:
		fmt.Println("i>10")
	case i > 5 && i <= 10:
		fmt.Println("5<i<=10")
	default:
		fmt.Println("i<=5")
	}

	switch j := 1; j {
	case 1:
		fallthrough
	case 2:
		fmt.Println("1")
	default:
		fmt.Println("没有匹配")
	}

	switch 2 > 1 {
	case true:
		fmt.Println("2>1")
	case false:
		fmt.Println("2<=1")
	}
}

func testFor() {
	sum := 0
	for i := 1; i <= 100; i++ {
		sum += i
	}
	fmt.Println("the sum is", sum)

	sum2 := 0
	i := 1
	// 无限循环
	for {
		sum2 += i
		i++
		if i > 100 {
			break
		}
	}

	fmt.Println("the sum2 is", sum2)
}

func testArray() {
	array := [5]string{"a", "b", "c", "d", "e"}
	fmt.Println(array[2])

	for i := 0; i < 5; i++ {
		fmt.Printf("数组索引:%d, 对应值:%s\n", i, array[i])
	}

	fmt.Println("-----------------")

	for i, v := range array {
		fmt.Printf("数组索引:%d, 对应值:%s\n", i, v)
	}

	fmt.Println("-----------------")

	for _, v := range array {
		fmt.Printf("对应值:%s\n", v)
	}
}

func testSlice() {
	array := [5]string{"a", "b", "c", "d", "e"}
	slice := array[2:5]
	fmt.Println(slice)

	slice[1] = "f"
	fmt.Println(array)
	fmt.Println(slice)

	// Go 语言在内存上划分了一块容量为 8 的内容空间（容量为 8），但是只有 4 个内存空间才有元素（长度为 4），其他的内存空间处于空闲状态，当通过 append 函数往切片中追加元素的时候，会追加到空闲的内存上，当切片的长度要超过容量的时候，会进行扩容。
	// slice2 :=make([]string,4,8)
	slice1 := []string{"a", "b", "c", "d", "e"}
	fmt.Println(len(slice1), cap(slice1))

	// append 会自动处理切片容量不足需要扩容的问题。
	//追加一个元素
	slice2 := append(slice1, "f")
	//多加多个元素
	// slice2 := append(slice1, "f", "g")
	//追加另一个切片
	// slice2 := append(slice1, slice...)
	fmt.Print(slice2)
}

func testMap() {
	// 它的 Key 类型为 string，Value 类型为 int。有了创建好的 map 变量，就可以对它进行操作了。
	nameAgeMap := make(map[string]int)
	//添加键值对或者更新对应 Key 的 Value
	nameAgeMap["xmw"] = 20
	//获取指定 Key 对应的 Value
	age := nameAgeMap["xmw"]
	fmt.Println("age: ", age)

	age, exist := nameAgeMap["xmw1"]
	if exist {
		fmt.Println(age)
	}

	// delete 有两个参数：第一个参数是 map，第二个参数是要删除键值对的 Key。
	delete(nameAgeMap, "test")

	for k, v := range nameAgeMap {
		fmt.Println("Key is", k, ",Value is", v)
	}

	fmt.Println(len(nameAgeMap))
}

func testString() {
	s := "Hello World 夏明尉"
	bs := []byte(s)
	fmt.Println(bs)
	fmt.Println(s[0], s[1], s[4])
	fmt.Println(len(s))

	// 想把一个汉字当成一个长度计算，可以使用 utf8.RuneCountInString 函数。
	fmt.Println(utf8.RuneCountInString(s))
}

func twoArray() {
	aa := [3][3]int{}
	aa[0][0] = 1
	aa[0][1] = 2
	aa[0][2] = 3
	aa[1][0] = 4
	aa[1][1] = 5
	aa[1][2] = 6
	aa[2][0] = 7
	aa[2][1] = 8
	aa[2][2] = 9
	fmt.Println(aa)
}
